package org.apache.ibatis.executor;

import org.apache.ibatis.cache.CacheKey;
import org.apache.ibatis.cache.impl.PerpetualCache;
import org.apache.ibatis.cursor.Cursor;
import org.apache.ibatis.executor.statement.StatementUtil;
import org.apache.ibatis.logging.Log;
import org.apache.ibatis.logging.LogFactory;
import org.apache.ibatis.logging.jdbc.ConnectionLogger;
import org.apache.ibatis.mapping.*;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.factory.ObjectFactory;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.LocalCacheScope;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.apache.ibatis.transaction.Transaction;
import org.apache.ibatis.type.TypeHandlerRegistry;

import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.List;
import java.util.concurrent.ConcurrentLinkedQueue;

import static org.apache.ibatis.executor.ExecutionPlaceholder.EXECUTION_PLACEHOLDER;

/**
 * BaseExecutor是一个实现了Executor接口的抽象类，它实现了Executor接口的大部分方法，其中就使用了模板方法模式。
 * 主要提供了缓存管理和事务管理的基本功能，继承BaseExecutor的子类只要实现四个基本方法
 * 来完成数据库的相关操作即可，这四个方法分别是:doUpdate()方法、doQuery()方法、doQueryCursor()方法、
 * doFlushStatement()方法，其余的功能在BaseExecutor中实现。
 *
 * @author Clinton Begin
 */
public abstract class BaseExecutor implements Executor {

    private static final Log log = LogFactory.getLog(BaseExecutor.class);

    /**
     * 实现事务的提交、回滚和关闭操作
     */
    protected Transaction transaction;
    /**
     * 其中封装的Executor对象
     */
    protected Executor wrapper;

    /**
     * 延迟加载队列
     */
    protected ConcurrentLinkedQueue<DeferredLoad> deferredLoads;
    /**
     * 一级缓存，用于缓存该Executor对象查询结果集映射得到的结果对象，
     * 一级缓存所存储从查询结果会在 MyBatis 执行更新操作（INSERT/UPDATE/DELETE），以及提交和回滚事务时被清空。
     */
    protected PerpetualCache localCache;
    /**
     * 一级缓存，用于缓存输出类型的参数
     */
    protected PerpetualCache localOutputParameterCache;
    /**
     * 全局MyBatis配置
     */
    protected Configuration configuration;

    /**
     * 用来记录嵌套查询的层数
     */
    protected int queryStack;
    /**
     * 是否关闭
     */
    private boolean closed;

    protected BaseExecutor(Configuration configuration, Transaction transaction) {
        this.transaction = transaction;
        this.deferredLoads = new ConcurrentLinkedQueue<DeferredLoad>();
        this.localCache = new PerpetualCache("LocalCache");
        this.localOutputParameterCache = new PerpetualCache("LocalOutputParameterCache");
        this.closed = false;
        this.configuration = configuration;
        this.wrapper = this;
    }

    @Override
    public Transaction getTransaction() {
        if (closed) {
            throw new ExecutorException("Executor was closed.");
        }
        return transaction;
    }

    /**
     * 首先会调用rollback()方法忽略缓存的SQL语句，之后根据参数决定是否关闭底层的数据库连接
     *
     * @param forceRollback
     */
    @Override
    public void close(boolean forceRollback) {
        try {
            try {
                rollback(forceRollback);
            } finally {
                if (transaction != null) {
                    transaction.close();
                }
            }
        } catch (SQLException e) {
            // Ignore.  There's nothing that can be done at this point.
            log.warn("Unexpected exception on closing transaction.  Cause: " + e);
        } finally {
            transaction = null;
            deferredLoads = null;
            localCache = null;
            localOutputParameterCache = null;
            closed = true;
        }
    }

    @Override
    public boolean isClosed() {
        return closed;
    }

    /**
     * 默认情况下，insert、update 和 delete 操作都会清空一二级缓存。
     */
    @Override
    public int update(MappedStatement ms, Object parameter) throws SQLException {
        ErrorContext.instance().resource(ms.getResource()).activity("executing an update").object(ms.getId());
        if (closed) {
            throw new ExecutorException("Executor was closed.");
        }
        // 刷新一级缓存
        // 调用localCache、localOutputParameterCache两个缓存的clear()方法完成清理工作。这是影响一级缓存中数据存活时长的第三个方面
        clearLocalCache();
        return doUpdate(ms, parameter);
    }

    /**
     * 可以缓存多条SQL语句，等待合适的时机将缓存的多条SQL语句一并发送到数据库执行。
     * Executor.flushStatements()方法主要是针对批处理多条SQL语句的，它会调用doFlushStatements()
     * 这个基本方法处理Executor中缓存的多条SQL语句
     *
     * @return
     * @throws SQLException
     */
    @Override
    public List<BatchResult> flushStatements() throws SQLException {
        return flushStatements(false);
    }

    public List<BatchResult> flushStatements(boolean isRollBack) throws SQLException {
        if (closed) {
            throw new ExecutorException("Executor was closed.");
        }
        // 调用doFlushStatements()这个基本方法，其参数isRollBack表示
        // 是否执行Executor中缓存的SQL语句，false表示执行，true表示不执行
        return doFlushStatements(isRollBack);
    }

    @Override
    public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
        BoundSql boundSql = ms.getBoundSql(parameter);
        // 根据查询语句及查询参数 创建 CacheKey
        CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
        return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
    }

    @SuppressWarnings("unchecked")
    @Override
    public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
        ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
        if (closed) {
            throw new ExecutorException("Executor was closed.");
        }
        if (queryStack == 0 && ms.isFlushCacheRequired()) {
            // 非嵌套查询，并且＜select＞节点配置的flushCache属性为true时，才会清空一级缓存
            // flushCache配置项是影响一级缓存中结果对象存活时长的第一个方面
            clearLocalCache();
        }
        List<E> list;
        try {
            // 增加查询层数
            queryStack++;
            // 查询一级缓存, 从一级缓存中获取缓存项
            list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
            if (list != null) {
                // 针对存储过程调用的处理，其功能是：在一级缓存命中时，获取缓存中保存的输出类型参数，
                // 并设置到用户传入的实参(parameter)对象中
                handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
            } else {
                // 一级缓存未命中，则从数据库中查询
                // 其中会调用doQuery()方法完成数据库查询，并得到映射后的结果对象，doQuery()方法是一个抽象方法，
                // 也是上述4个基本方法之一，由BaseExecutor的子类具体实现。
                list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
            }
        } finally {
            // 当前查询完成，查询层数减少
            queryStack--;
        }
        if (queryStack == 0) {
            // 在最外层的查询结束时，所有嵌套查询也已经完成，相关缓存项也已经完全加载，所以在这里可以触发DeferredLoad加载一级缓存中记录的嵌套查询的结果对象
            for (DeferredLoad deferredLoad : deferredLoads) {
                deferredLoad.load();
            }
            // issue #601
            // 加裁完成后，清空deferredLoads集合
            deferredLoads.clear();
            if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
                // issue #482
                // 根据localCacheScope配置决定是否清空一级缓存，localCacheScope配置是影响一级缓存中结果对象存活时长的第二个方面
                clearLocalCache();
            }
        }
        return list;
    }

    @Override
    public <E> Cursor<E> queryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds) throws SQLException {
        BoundSql boundSql = ms.getBoundSql(parameter);
        return doQueryCursor(ms, parameter, rowBounds, boundSql);
    }

    @Override
    public void deferLoad(MappedStatement ms, MetaObject resultObject, String property, CacheKey key, Class<?> targetType) {
        if (closed) {
            throw new ExecutorException("Executor was closed.");
        }
        DeferredLoad deferredLoad = new DeferredLoad(resultObject, property, key, localCache, configuration, targetType);
        if (deferredLoad.canLoad()) {
            // 一级缓存中已经记录了指定查询的结果对象，直接从缓存中加载对象，并设置到外层对象中
            deferredLoad.load();
        } else {
            // 将DeferredLoad对象添加到deferredLoads队列中，待整个外层查询结束后，再加载该结果对象
            deferredLoads.add(new DeferredLoad(resultObject, property, key, localCache, configuration, targetType));
        }
    }

    /**
     * 该CacheKey对象由MappedStatement的id、对应的offset和limit、SQL语句（包含"?"占位符）、
     * 用户传递的实参以及Environment的id这五部分构成
     * 所以，我们可以简单的把 CacheKey 看做是一个查询请求的 id。
     * 有了 CacheKey，我们就可以使用它读写缓存了。
     *
     * @param ms
     * @param parameterObject
     * @param rowBounds
     * @param boundSql
     * @return
     */
    @Override
    public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) {
        if (closed) {
            throw new ExecutorException("Executor was closed.");
        }

        // 创建 CacheKey 对象
        CacheKey cacheKey = new CacheKey();
        // 将 MappedStatement 的 id 作为影响因子进行计算
        cacheKey.update(ms.getId());
        // RowBounds 用于分页查询，下面将它的两个字段作为影响因子进行计算
        cacheKey.update(rowBounds.getOffset());
        cacheKey.update(rowBounds.getLimit());
        // 获取 sql 语句，并进行计算
        cacheKey.update(boundSql.getSql());

        List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
        TypeHandlerRegistry typeHandlerRegistry = ms.getConfiguration().getTypeHandlerRegistry();
        // mimic DefaultParameterHandler logic
        for (ParameterMapping parameterMapping : parameterMappings) {
            if (parameterMapping.getMode() != ParameterMode.OUT) {
                // 运行时参数
                Object value;
                // 当前大段代码用于获取 SQL 中的占位符 #{xxx} 对应的运行时参数，
                String propertyName = parameterMapping.getProperty();
                if (boundSql.hasAdditionalParameter(propertyName)) {
                    value = boundSql.getAdditionalParameter(propertyName);
                } else if (parameterObject == null) {
                    value = null;
                } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
                    value = parameterObject;
                } else {
                    MetaObject metaObject = configuration.newMetaObject(parameterObject);
                    value = metaObject.getValue(propertyName);
                }
                // 让运行时参数参与计算
                cacheKey.update(value);
            }
        }
        if (configuration.getEnvironment() != null) {
            // 获取 Environment id 遍历，并让其参与计算
            cacheKey.update(configuration.getEnvironment().getId());
        }
        return cacheKey;
    }

    @Override
    public boolean isCached(MappedStatement ms, CacheKey key) {
        // 检测缓存中是否缓存了CacheKey对应的对象
        return localCache.getObject(key) != null;
    }

    /**
     * 首先会清空一级缓存、调用flushStatements()方法，最后才根据参数决定是否真正提交事务
     *
     * @param required
     * @throws SQLException
     */
    @Override
    public void commit(boolean required) throws SQLException {
        if (closed) {
            throw new ExecutorException("Cannot commit, transaction is already closed");
        }
        // 清空一级缓存
        clearLocalCache();
        // 处理Executor中缓存的多条SQL语句
        flushStatements();
        if (required) {
            transaction.commit();
        }
    }

    @Override
    public void rollback(boolean required) throws SQLException {
        if (!closed) {
            try {
                // 清空一级缓存
                clearLocalCache();
                // isRollBack参数为true，这就会导致Executor中缓存的SQL语句全部被忽略(不会被发送到数据库执行)
                flushStatements(true);
            } finally {
                if (required) {
                    transaction.rollback();
                }
            }
        }
    }

    @Override
    public void clearLocalCache() {
        if (!closed) {
            localCache.clear();
            localOutputParameterCache.clear();
        }
    }

    protected abstract int doUpdate(MappedStatement ms, Object parameter)
            throws SQLException;

    protected abstract List<BatchResult> doFlushStatements(boolean isRollback)
            throws SQLException;

    protected abstract <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql)
            throws SQLException;

    protected abstract <E> Cursor<E> doQueryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds, BoundSql boundSql)
            throws SQLException;

    protected void closeStatement(Statement statement) {
        if (statement != null) {
            try {
                if (!statement.isClosed()) {
                    statement.close();
                }
            } catch (SQLException e) {
                // ignore
            }
        }
    }

    /**
     * Apply a transaction timeout.
     *
     * @param statement a current statement
     * @throws SQLException if a database access error occurs, this method is called on a closed <code>Statement</code>
     * @see StatementUtil#applyTransactionTimeout(Statement, Integer, Integer)
     * @since 3.4.0
     */
    protected void applyTransactionTimeout(Statement statement) throws SQLException {
        StatementUtil.applyTransactionTimeout(statement, statement.getQueryTimeout(), transaction.getTimeout());
    }

    private void handleLocallyCachedOutputParameters(MappedStatement ms, CacheKey key, Object parameter, BoundSql boundSql) {
        if (ms.getStatementType() == StatementType.CALLABLE) {
            final Object cachedParameter = localOutputParameterCache.getObject(key);
            if (cachedParameter != null && parameter != null) {
                final MetaObject metaCachedParameter = configuration.newMetaObject(cachedParameter);
                final MetaObject metaParameter = configuration.newMetaObject(parameter);
                for (ParameterMapping parameterMapping : boundSql.getParameterMappings()) {
                    if (parameterMapping.getMode() != ParameterMode.IN) {
                        final String parameterName = parameterMapping.getProperty();
                        final Object cachedValue = metaCachedParameter.getValue(parameterName);
                        metaParameter.setValue(parameterName, cachedValue);
                    }
                }
            }
        }
    }

    private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
        List<E> list;
        // 在缓存中添加占位符
        localCache.putObject(key, EXECUTION_PLACEHOLDER);
        try {
            // 调用doQuery()方法(抽象方法)，完成数据库查询操作，并返回结果对象
            list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
        } finally {
            // 在缓存中删除占位符
            localCache.removeObject(key);
        }
        // 将真正的结果对象添加到一级缓存中
        localCache.putObject(key, list);
        // 是否为存储过程调用
        if (ms.getStatementType() == StatementType.CALLABLE) {
            // 缓存输出类型的参数
            localOutputParameterCache.putObject(key, parameter);
        }
        return list;
    }

    protected Connection getConnection(Log statementLog) throws SQLException {
        Connection connection = transaction.getConnection();
        if (statementLog.isDebugEnabled()) {
            return ConnectionLogger.newInstance(connection, statementLog, queryStack);
        } else {
            return connection;
        }
    }

    @Override
    public void setExecutorWrapper(Executor wrapper) {
        this.wrapper = wrapper;
    }

    /**
     * 它负责从localCache缓存中延迟加载结果对象
     */
    private static class DeferredLoad {

        private final MetaObject resultObject;
        private final String property;
        private final Class<?> targetType;
        private final CacheKey key;
        private final PerpetualCache localCache;
        private final ObjectFactory objectFactory;
        private final ResultExtractor resultExtractor;

        // issue #781
        public DeferredLoad(MetaObject resultObject,
                            String property,
                            CacheKey key,
                            PerpetualCache localCache,
                            Configuration configuration,
                            Class<?> targetType) {
            this.resultObject = resultObject;
            this.property = property;
            this.key = key;
            this.localCache = localCache;
            this.objectFactory = configuration.getObjectFactory();
            this.resultExtractor = new ResultExtractor(configuration, objectFactory);
            this.targetType = targetType;
        }

        /**
         * 负责检测缓存项是否已经完全加载到了缓存中,
         * 首先要说明"完全加载"的含义：BaseExecutor.queryFromDatabase()方法中，开始查询调用doQuery()方法查询数据库之前，
         * 会先在localCache中添加占位符，待查询完成之后，才将真正的结果对象放到localCache中缓存，此时该缓存项才算"完全加载"
         *
         * @return
         */
        public boolean canLoad() {
            // 检测缓存是否存在指定的结果对象 、检测是否为占位符
            return localCache.getObject(key) != null && localCache.getObject(key) != EXECUTION_PLACEHOLDER;
        }

        public void load() {
            @SuppressWarnings("unchecked")
            // we suppose we get back a List
                    // 从缓存中查询指定的结果对象
                    List<Object> list = (List<Object>) localCache.getObject(key);
            // 将缓存的结果对象转换成指定类型
            Object value = resultExtractor.extractObjectFromList(list, targetType);
            // 设置到外层对象的对应属性
            resultObject.setValue(property, value);
        }

    }

}
